Subsystem 1b-ii: live connection lifecycle, failover & #info status - #6
Merged
Conversation
Replace IsHealthy (bool) with Status (ConnectionStatus enum) and add nullable PlayerCount; generate LiveConnections migration and schema test.
… pool Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Notifier Introduces IUserDmSender (Discord project) as a reusable DM primitive and replaces DiscordOwnerNotifier's direct DiscordSocketClient usage with it; fixes PairingRegistrationTests to supply a substitute IUserDmSender. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…over, restart-survival) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives IConnectionSupervisor: StartAllAsync on startup, reacts to ServerRegisteredEvent to call EnsureConnectionAsync, StopAllAsync on shutdown. Added InternalsVisibleTo DynamicProxyGenAssembly2 to allow NSubstitute to proxy the internal IConnectionSupervisor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ConnectionComponentModule handling workspace:info:swap:* select menus - Add ConnectionServiceCollectionExtensions.AddConnections() DI extension - Wire ConnectionOptions validation and AddConnections() into Program.cs - Add project reference for Connections in Host.csproj - Add ConnectionRegistrationTests verifying IConnectionSupervisor and IConnectionStore resolve Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tbeat timeout < interval
There was a problem hiding this comment.
Pull request overview
Builds a new live Rust+ connection subsystem (one socket per (guild, server)) with persisted connection state, automatic reconnect/failover, and Workspace #info live-status rendering (including a ManageGuild swap-select for the active credential).
Changes:
- Adds
RustPlusBot.Features.Connections(socket source seam, supervisor loop, hosted service, and swap interaction module). - Introduces persistence support for live connection status + credential pool ops via
IConnectionStoreand aLiveConnectionsmigration. - Updates Workspace
#inforendering to show live connection status/player info and to refresh onConnectionStatusChangedEvent; refactors owner DM sending behindIUserDmSender.
Reviewed changes
Copilot reviewed 48 out of 49 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/RustPlusBot.Persistence.Tests/PersistenceRegistrationTests.cs | Verifies IConnectionStore DI registration |
| tests/RustPlusBot.Persistence.Tests/Connections/ConnectionStoreTests.cs | Tests connection store pool + status ops |
| tests/RustPlusBot.Persistence.Tests/Connections/ConnectionStateSchemaTests.cs | Verifies EF schema round-trip for new fields |
| tests/RustPlusBot.Features.Workspace.Tests/Messages/RendererTests.cs | Updates #info rendering tests for live status |
| tests/RustPlusBot.Features.Workspace.Tests/Hosting/WorkspaceConnectionStatusTests.cs | Tests workspace refresh on status-change event |
| tests/RustPlusBot.Features.Pairing.Tests/PairingRegistrationTests.cs | Adds IUserDmSender test DI seam |
| tests/RustPlusBot.Features.Pairing.Tests/DiscordOwnerNotifierTests.cs | Tests notifier uses IUserDmSender |
| tests/RustPlusBot.Features.Connections.Tests/TestDb.cs | SQLite test DB helper for Connections tests |
| tests/RustPlusBot.Features.Connections.Tests/SeamSmokeTests.cs | Smoke test for fake socket source seam |
| tests/RustPlusBot.Features.Connections.Tests/RustPlusSocketSourceTests.cs | Construction test for RustPlusApi adapter |
| tests/RustPlusBot.Features.Connections.Tests/RustPlusBot.Features.Connections.Tests.csproj | New Connections test project |
| tests/RustPlusBot.Features.Connections.Tests/Fakes/FakeRustSocketSource.cs | Scripted fake socket source for supervisor tests |
| tests/RustPlusBot.Features.Connections.Tests/ConnectionSupervisorTests.cs | End-to-end supervisor lifecycle/failover tests |
| tests/RustPlusBot.Features.Connections.Tests/ConnectionRegistrationTests.cs | Verifies Connections DI resolves |
| tests/RustPlusBot.Features.Connections.Tests/ConnectionHostedServiceTests.cs | Tests hosted service starts/stops supervisor |
| src/RustPlusBot.Persistence/PersistenceServiceCollectionExtensions.cs | Registers IConnectionStore in persistence DI |
| src/RustPlusBot.Persistence/Migrations/BotDbContextModelSnapshot.cs | Updates snapshot for status/player count |
| src/RustPlusBot.Persistence/Migrations/20260615134626_LiveConnections.Designer.cs | New migration designer output |
| src/RustPlusBot.Persistence/Migrations/20260615134626_LiveConnections.cs | Migration: rename IsHealthy→Status, add PlayerCount |
| src/RustPlusBot.Persistence/Connections/IConnectionStore.cs | New persistence API for connection state + pool ops |
| src/RustPlusBot.Persistence/Connections/ConnectionStore.cs | EF implementation of connection store |
| src/RustPlusBot.Host/RustPlusBot.Host.csproj | References Connections feature project |
| src/RustPlusBot.Host/Program.cs | Binds/validates ConnectionOptions, wires AddConnections() |
| src/RustPlusBot.Features.Workspace/WorkspaceComponentIds.cs | Adds swap-select custom-id prefix |
| src/RustPlusBot.Features.Workspace/RustPlusBot.Features.Workspace.csproj | Adds proxy visibility for NSubstitute |
| src/RustPlusBot.Features.Workspace/Messages/ServerInfoMessageRenderer.cs | Renders live status + swap select in #info |
| src/RustPlusBot.Features.Workspace/Localization/LocalizationCatalog.cs | Adds localization keys for new #info fields |
| src/RustPlusBot.Features.Workspace/Hosting/WorkspaceHostedService.cs | Adds consumer loop for connection-status events |
| src/RustPlusBot.Features.Pairing/Notifications/DiscordOwnerNotifier.cs | Refactors notifier to use IUserDmSender |
| src/RustPlusBot.Features.Connections/Supervisor/IConnectionSupervisor.cs | New supervisor abstraction |
| src/RustPlusBot.Features.Connections/Supervisor/ConnectionSupervisor.cs | Core connect/heartbeat/reconnect/failover loop |
| src/RustPlusBot.Features.Connections/RustPlusBot.Features.Connections.csproj | New Connections feature project |
| src/RustPlusBot.Features.Connections/Modules/ConnectionComponentModule.cs | Implements ManageGuild swap-select handler |
| src/RustPlusBot.Features.Connections/Listening/SocketConnectOutcome.cs | Connect outcome classification enum |
| src/RustPlusBot.Features.Connections/Listening/RustPlusSocketSource.cs | RustPlusApi-backed socket adapter |
| src/RustPlusBot.Features.Connections/Listening/IRustSocketSource.cs | Factory seam for socket creation |
| src/RustPlusBot.Features.Connections/Listening/IRustServerConnection.cs | Abstraction for a live server socket |
| src/RustPlusBot.Features.Connections/Listening/HeartbeatResult.cs | Heartbeat result classification |
| src/RustPlusBot.Features.Connections/Hosting/ConnectionHostedService.cs | Hosted service driving supervisor lifecycle |
| src/RustPlusBot.Features.Connections/ConnectionServiceCollectionExtensions.cs | DI registration for Connections feature |
| src/RustPlusBot.Features.Connections/ConnectionOptions.cs | Config options for timeouts/backoff/heartbeat |
| src/RustPlusBot.Domain/Connections/ConnectionStatus.cs | New status enum persisted in connection state |
| src/RustPlusBot.Domain/Connections/ConnectionState.cs | Reshapes state: adds Status + PlayerCount |
| src/RustPlusBot.Discord/Notifications/IUserDmSender.cs | New DM-sending abstraction |
| src/RustPlusBot.Discord/Notifications/DiscordUserDmSender.cs | Discord.Net implementation of DM sender |
| src/RustPlusBot.Discord/DiscordServiceCollectionExtensions.cs | Registers IUserDmSender in Discord DI |
| src/RustPlusBot.Abstractions/Events/ConnectionStatusChangedEvent.cs | New event for #info refresh |
| RustPlusBot.slnx | Adds Connections projects to solution |
| Directory.Packages.props | Adds RustPlusApi 2.0.0-beta.1 package version |
Files not reviewed (1)
- src/RustPlusBot.Persistence/Migrations/20260615134626_LiveConnections.Designer.cs: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+63
to
+74
| public async Task EnsureConnectionAsync(ulong guildId, Guid serverId, CancellationToken cancellationToken = default) | ||
| { | ||
| var key = (guildId, serverId); | ||
| await StopConnectionAsync(key).ConfigureAwait(false); | ||
| if (_shutdown.IsCancellationRequested) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var cts = CancellationTokenSource.CreateLinkedTokenSource(_shutdown.Token); | ||
| _connections[key] = new Handle(cts, Task.Run(() => RunAsync(key, cts.Token), CancellationToken.None)); | ||
| } |
Comment on lines
+12
to
+21
| public IRustServerConnection Create(string ip, int port, ulong steamId, string playerToken) | ||
| { | ||
| if (!int.TryParse(playerToken, CultureInfo.InvariantCulture, out var token)) | ||
| { | ||
| throw new ArgumentException($"playerToken is not a valid numeric token: '{playerToken}'.", | ||
| nameof(playerToken)); | ||
| } | ||
|
|
||
| return new RustPlusServerConnection(ip, port, steamId, token, logger); | ||
| } |
Comment on lines
+30
to
+36
| if (!Guid.TryParse(serverIdRaw, out var serverId) | ||
| || selectedValues.Length == 0 | ||
| || !Guid.TryParse(selectedValues[0], out var credentialId)) | ||
| { | ||
| await RespondAsync("That selection wasn't valid.", ephemeral: true).ConfigureAwait(false); | ||
| return; | ||
| } |
… gate, no token leak, swap value validation
HandyS11
force-pushed
the
feat/live-connection
branch
from
June 15, 2026 16:14
c45796a to
d1eeec1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds the live Rust+ connection layer on top of 1b-i's credential pool: one live socket per
(guild, server)driven by the pool'sActiveplayer credential, with restart-survival, auto-failover, heartbeat health, a ManageGuild swap control on#info, and a live-status#infoembed.New
RustPlusBot.Features.Connectionsproject (mirrorsFeatures.Pairing):IRustSocketSource/RustPlusSocketSource— integration shim overRustPlusApi2.0.0-beta.1(construction-tested only; auth-rejection error mapping is a documentedVERIFYfor live tuning).ConnectionSupervisor(singleton) — oneconnect → heartbeat → reconnect/failoverloop per server; auto-promotes the nextStandbyon auth-reject, marks the dead credentialInvalid+ DMs its owner, capped exponential backoff on unreachable, restart-survival viaStartAllAsync. PublishesConnectionStatusChangedEventonly on a real state change.ConnectionHostedService—StartAllon startup + consumesServerRegisteredEvent;StopAllon shutdown.ConnectionComponentModule— ManageGuild#infoswap-select handler.Supporting changes:
ConnectionStatereshaped (dropIsHealthy; addStatusenum +PlayerCount) — now the restart-surviving source of truth. MigrationLiveConnections.IConnectionStore(Persistence): connection-state + pool ops (GetActiveCredential/ListPool/Promote/MarkInvalid/ListConnectableServers/UpsertStatuswith a changed-flag).IUserDmSenderextracted into the Discord project;DiscordOwnerNotifierrefactored onto it.ServerInfoMessageRendererrewritten for live status + the swap select (workspace:info:swap:{serverId});WorkspaceHostedServicegained a second consumer forConnectionStatusChangedEvent→ re-renders#info.ConnectionOptions(timeouts/backoff/heartbeat) bound + validatedValidateOnStart;AddConnections()wired in the Host.Deferred to 1b-iii (intentionally not built): IP-change re-identification,
RustServer-removal FK/cascade cleanup, account disconnect/remove flow (FcmRegistrationStatus.Disabled).Test Plan
dotnet build RustPlusBot.slnx→ 0 warnings / 0 errors (strict analyzers)dotnet test RustPlusBot.slnx→ 104 passed (Connections 10, Workspace 33, Persistence 37, Pairing 20, Abstractions 4)dotnet jb cleanupcode --profile=ReformatAndReorderapplieddotnet ef migrations has-pending-model-changes→ no driftRustPlusApiadapter's auth-rejection mapping against a live server (documentedVERIFYinRustPlusSocketSource)🤖 Generated with Claude Code